﻿using System;
using System.Web.Mvc;
using DotNetOpenAuth.OpenId.RelyingParty;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.Messaging;
using MvcContrib;
using MvcContrib.PortableAreas;
using OpenIdPortableArea.Messages;
using OpenIdPortableArea.Areas.OpenId.Models;
using OpenIdPortableArea.Areas.OpenId.Services;
using System.Web;

namespace OpenIdPortableArea.Areas.OpenId.Controllers
{
    /// <summary>
    /// OpenIdController
    /// </summary>
    public class OpenIdController : Controller
    {
        const string OpenIdUrlFormKey = "openIdUrl";
        private readonly IAuthenticationService _authenticationService;
        private readonly IApplicationBus _bus;

        /// <summary>
        /// The last IEventMessage to be sent over the IApplicationBus
        /// </summary>
        public IEventMessage LastSentMessage { get; protected set; }

        /// <summary>
        /// The current Http Request
        /// </summary>
        public HttpRequestBase HttpRequest { get; set; }

        /// <summary>
        /// Initializes the object with its default AuthenticationService and EventMessageBusService
        /// </summary>
        public OpenIdController() : this(new AuthenticationService(), Bus.Instance) { }

        /// <summary>
        /// Initializes the object with the provided services
        /// </summary>
        /// <param name="authenticationService"></param>
        /// <param name="bus"></param>
        public OpenIdController(IAuthenticationService authenticationService, IApplicationBus bus)
        {
            _authenticationService = authenticationService;
            _bus = bus;
        }

        /// <summary>
        /// Displays the Logout View
        /// </summary>
        /// <returns></returns>
        public virtual ActionResult Logout(string returnUrl)
        {
            var message = new LoggingOutMessage {Message = returnUrl};
            Send(message);
            if (!message.AutoLogout)
                return View("Logout", new object());

            return Logout();
        }

        /// <summary>
        /// Sends a message over the IApplicationBus, also sets LastSentMessage.
        /// </summary>
        /// <param name="message"></param>
        private void Send(IEventMessage message)
        {
            _bus.Send(message);
            LastSentMessage = message;
        }

        /// <summary>
        /// Logs out the current user
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public virtual ActionResult Logout()
        {
            var message = new LoggedOutMessage();
            Send(message);

            if (message.UseDefaultLogoutMethod)
                _authenticationService.Logout();

            return Redirect(message.ReturnUrl);
        }

        /// <summary>
        /// Returns the Login View
        /// </summary>
        /// <returns></returns>
        public virtual ActionResult Login(string returnUrl)
        {
            return View("Login", new LoginInput { ReturnUrl = returnUrl });
        }

        /// <summary>
        /// Redirects the user to their OpenId Provider
        /// </summary>
        /// <param name="loginInput"></param>
        /// <returns></returns>
        [HttpPost, ValidateInput(false)]
        public virtual ActionResult Login(LoginInput loginInput)
        {
            Uri callbackUrl = GetCallBackUrl(loginInput.ReturnUrl);

            bool isValid = _authenticationService.IsValid(loginInput.OpenIdUrl, callbackUrl);

            if (!isValid)
            {
                AddError("The specified OpenID URL is invalid.  Please check the URL and resubmit.");
                return View("Login", loginInput);
            }

            var message = new ClaimsRequestMessage {Claim = new ClaimsRequest()};

            Send(message);

            try
            {
                _authenticationService.AddClaim(message.Claim);
                return _authenticationService.Redirect();
            }
            catch (ProtocolException ex)
            {
                AddError(ex.Message);
            }
            catch
            {
                AddError("There was an error processing the request.");
            }

            return View("Login", loginInput);
        }

        /// <summary>
        /// Callback Action for the OpenId Provider.  Sends a Message regarding the Status of the OpenId Provider's response.
        /// </summary>
        /// <param name="loginInput"></param>
        /// <returns></returns>
        public virtual ActionResult Result(LoginInput loginInput)
        {
            AuthenticationStatus status = _authenticationService.GetAuthenticationStatus();
            switch (status)
            {
                case AuthenticationStatus.Authenticated:
                    string url = string.IsNullOrEmpty(loginInput.ReturnUrl) ? "~/OpenId/Success" : loginInput.ReturnUrl;

                    var message = new AuthenticatedMessage {Result = new RedirectResult(url)};
                    
                    _authenticationService.SetMessage(message);

                    Send(message);

                    return message.Result;

                case AuthenticationStatus.Canceled:
                    var canceledMessage = new UnauthenticatedMessage("Canceled");
                    Send(canceledMessage);
                    AddError("The OpenID authentication was cancelled.");
                    break;

                case AuthenticationStatus.Failed:
                    var failedMessage = new UnauthenticatedMessage("Failed");
                    Send(failedMessage);
                    AddError("The OpenID authentication failed.");
                    break;
            }

            return View("Login", loginInput);
        }

        /// <summary>
        /// Displays a view to the user indicating that they were successfully logged in.
        /// </summary>
        /// <returns></returns>
        public ActionResult Success()
        {
            return View();
        }

        /* Widget Actions
        ---------------------------------------------------------------------*/

        /// <summary>
        /// Returns a partial view containing OpenId Providers
        /// </summary>
        /// <returns></returns>
        public virtual ActionResult ProvidersWidget()
        {
            return PartialView();
        }

        /// <summary>
        /// Returns a partial view that is a simple form that contains
        /// a text box and button, and posts back to Login.
        /// </summary>
        /// <returns></returns>
        public virtual ActionResult LoginWidget(LoginInput loginInput)
        {
            return PartialView(loginInput);
        }

        /// <summary>
        /// Returns a partial view that displays the status of the
        /// currently logged in user.
        /// </summary>
        /// <returns></returns>
        public virtual ActionResult LoginStatusWidget()
        {
            return PartialView();
        }

        /// <summary>
        /// Adds a ModelState Error using OpenIdUrlFormKey.
        /// </summary>
        /// <param name="message"></param>
        protected virtual void AddError(string message)
        {
            ModelState.AddModelError(OpenIdUrlFormKey, message);
        }

        /// <summary>
        /// Builds a Callback url
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public virtual Uri GetCallBackUrl(string url)
        {
            var values = new { action = "Result", returnUrl = url };

            var request = HttpRequest ?? Request;

            string uri = Url.RouteUrl("OpenId", values);
            Uri appUri = GetAppRoot(request);
            return new Uri(appUri, uri);
        }

        /// <summary>
        /// Get the application root based on the request URL
        /// </summary>
        /// <param name="request">The HttpContext for this request</param>
        /// <returns>The application root URI based on the request URL</returns>
        protected virtual Uri GetAppRoot(HttpRequestBase request)
        {
            var hostHeader = request.Headers["HOST"];

            if(!hostHeader.StartsWith(request.Url.Scheme))
            {
                hostHeader = request.Url.Scheme + "://" + hostHeader;
            }

            if (String.IsNullOrWhiteSpace(hostHeader))
            {
                return new Uri(request.Url, request.ApplicationPath);
            }
            else
            {
                return new Uri(new Uri(hostHeader), request.ApplicationPath);
            }
        }
    }
}